﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Engine.Dtd.Schema;
using System.Diagnostics;

namespace Engine.Sgml {
    internal class SgmlTextWriter : IDisposable {
        protected TextWriter writer;

        public SgmlTextWriter()
            : this(new StringWriter()) {
            }

        public SgmlTextWriter(TextWriter writer) {
            Guard.ArgumentNotNull(writer, "writer");
            this.writer = writer;
            }

        public override string ToString() {
            return writer.ToString();
            }

        public void Flush() {
            writer.Flush();
            }

        public void WriteDocument(SElement document) {
            Debug.Assert(document.NodeType == SgmlNodeType.Document);
            foreach (SElement node in document.Nodes)
                WriteInternal(node);
            }

        public void WriteAttribute(SName name, string value) {
            writer.Write(Characters.Whitespace);
            writer.Write(name);
            writer.Write(Characters.EqualsChar);
            writer.Write(Characters.AttributeQuotationMark);
            if (value != null)
                SgmlTextEncoder.EncodeAttributeValue(value, writer);
            writer.Write(Characters.AttributeQuotationMark);
            }

        public void WriteDataSection(SName name, string data) {
            Guard.ArgumentNotNull(name, "name");
            Guard.ArgumentNotNull(data, "data");
            Debug.Assert(name != null && name != SName.Empty);
            writer.Write(Characters.StartTagMarkup);
            writer.Write(Characters.ExclamationPoint);
            writer.Write(Characters.LeftBracket);
            writer.Write(name);
            writer.Write(Characters.LeftBracket);
            WriteCData(data);
            writer.Write(Characters.RightBracket);
            writer.Write(Characters.RightBracket);
            writer.Write(Characters.EndTagMarkup);
            }

        public void WriteConditional(string name, string value) {
            writer.Write(Characters.StartTagMarkup);
            writer.Write(Characters.ExclamationPoint);
            writer.Write(Characters.LeftBracket);
            writer.Write(name);
            if (!string.IsNullOrWhiteSpace(value)) {
                writer.Write(Characters.Whitespace);
                writer.Write(value);
                }
            writer.Write(Characters.RightBracket);
            writer.Write(Characters.EndTagMarkup);
            }

        public void WriteProcessingInstruction(SElement element) {
            writer.Write(Characters.StartTagMarkup);
            writer.Write(Characters.ProcessingInstructionMark);
            writer.Write(element.Name.ToString());
            writer.Write(Characters.Whitespace);
            writer.Write(element.Value);
            writer.Write(Characters.ProcessingInstructionMark);
            writer.Write(Characters.EndTagMarkup);
            }

        public void WriteComment(string value) {
            writer.Write(Characters.StartTagMarkup);
            writer.Write(Characters.ExclamationPoint);
            writer.Write(Characters.CommentMarkup);
            writer.Write(Characters.CommentMarkup);
            WriteCommentValue(value);
            writer.Write(Characters.CommentMarkup);
            writer.Write(Characters.CommentMarkup);
            writer.Write(Characters.EndTagMarkup);
            }

        public void WriteStartElement(SElement element) {
            writer.Write(Characters.StartTagMarkup);
            writer.Write(element.Name.ToString());
            foreach (SAttribute attribute in element.Attributes)
                WriteAttribute(attribute.Name, attribute.Value);
            if (element.HasElements) {
                writer.Write(Characters.EndTagMarkup);
                }
            else if (element.ContentModelType() == DContentModelType.Empty) {
                writer.Write(Characters.ClosingTagMarkup);
                writer.Write(Characters.EndTagMarkup);
                }
            else {
                writer.Write(Characters.EndTagMarkup);
                WriteEndElement(element.Name);
                }
            }

        public void WriteEndElement(SName name) {
            writer.Write(Characters.StartTagMarkup);
            writer.Write(Characters.ClosingTagMarkup);
            writer.Write(name.ToString());
            writer.Write(Characters.EndTagMarkup);
            }

        public virtual void WriteDoctype(SElement doctype) {
            writer.Write(Characters.StartTagMarkup);
            writer.Write(Characters.ExclamationPoint);
            writer.Write("DOCTYPE");
            if (doctype.LocalName.Length > 0) {
                writer.Write(Characters.Whitespace);
                writer.Write(doctype.LocalName);
                }
            foreach (SAttribute item in doctype.Attributes) {
                if (string.Equals(item.LocalName, "SYSTEM", SObject.NameComparisonType)) {
                    if (string.IsNullOrWhiteSpace(doctype.Value)) {
                        writer.Write(Characters.Whitespace);
                        writer.Write(Characters.QuotationMark);
                        writer.Write(item.Value);
                        writer.Write(Characters.QuotationMark);
                        }
                    }
                else if (!string.IsNullOrWhiteSpace(item.Value)) {
                    writer.Write(Characters.Whitespace);
                    writer.Write(item.LocalName.ToUpper());
                    writer.Write(Characters.Whitespace);
                    writer.Write(Characters.QuotationMark);
                    writer.Write(item.Value);
                    writer.Write(Characters.QuotationMark);
                    }
                }
            if (!string.IsNullOrWhiteSpace(doctype.Value)) {
                writer.Write(Characters.Whitespace);
                writer.Write(Characters.LeftBracket);
                writer.Write(doctype.Value);
                writer.Write(Characters.RightBracket);
                }
            writer.Write(Characters.EndTagMarkup);
            }

        public virtual void WriteElement(SElement element) {
            WriteStartElement(element);
            if (element.HasElements) {
                foreach (SElement item in element.Nodes) {
                    WriteInternal(item);
                    }
                WriteEndElement(element.Name);
                }
            }

        public virtual void WriteText(string text) {
            if (text != null)
                for (int i = 0; i < text.Length; i++) {
                    SgmlTextEncoder.EncodeChar(text[i], writer);
                    }
            }

        public virtual void WriteSignificantWhiteSpace(string whitespace) {
            if (whitespace != null)
                writer.Write(whitespace);
            }

        public virtual void WriteWhiteSpace(string value) {
            if (value != null)
                writer.Write(value);
            }

        protected virtual void WriteCData(string data) {
            if (data != null)
                writer.Write(data);
            }

        protected virtual void WriteCommentValue(string value) {
            if (value != null)
                writer.Write(value);
            }

        protected void WriteInternal(SElement element) {
            switch (element.NodeType) {
                case SgmlNodeType.Attribute:
                    WriteAttribute(element.Name, element.Value);
                    break;
                case SgmlNodeType.CData:
                    WriteCData(element.Value);
                    break;
                case SgmlNodeType.CDataSection:
                    WriteDataSection(element.Name, element.Value);
                    break;
                case SgmlNodeType.Condition:
                    WriteConditional(element.LocalName, element.Value);
                    break;
                case SgmlNodeType.ProcessingInstruction:
                    WriteProcessingInstruction(element);
                    break;
                case SgmlNodeType.Comment:
                    WriteComment(element.Value);
                    break;
                case SgmlNodeType.Document:
                    throw new InvalidOperationException();
                case SgmlNodeType.DocumentType:
                    WriteDoctype(element);
                    break;
                case SgmlNodeType.Element:
                    WriteElement(element);
                    break;
                case SgmlNodeType.EndElement:
                    break;
                case SgmlNodeType.None:
                    break;
                case SgmlNodeType.Text:
                    WriteText(element.Value);
                    break;
                case SgmlNodeType.WhiteSpace:
                    WriteWhiteSpace(element.Value);
                    break;
                case SgmlNodeType.SignificantWhiteSpace:
                    WriteSignificantWhiteSpace(element.Value);
                    break;
                default:
                    throw new InvalidOperationException("Unknown the node type of an element.");
                }
            }

        public void Dispose() {
            Dispose(true);
            }

        protected virtual void Dispose(bool disposing) {
            writer.Close();
            }

        ~SgmlTextWriter() {
            Dispose(false);
            }
        }
    }
